﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ServerApp
{
    class Field
    {
        class Spot
        {
            private int row, position;  //координаты
            string owner;               //владелец

            /// <summary>
            /// Конструктор
            /// </summary>
            /// <param name="r">ряд</param>
            /// <param name="p">позиция</param>
            public Spot(int r, int p) : this(r, p, string.Empty) { }
            /// <summary>
            /// Конструктор
            /// </summary>
            /// <param name="r">ряд</param>
            /// <param name="p">позиция</param>
            /// <param name="o">владелец</param>
            public Spot(int r, int p, string o)
            {
                row = r;
                position = p;
                owner = o;
            }

            /// <summary>
            /// Назначает владельца точке
            /// </summary>
            /// <param name="player">новый владелец</param>
            public void TakePoint(string player)
            { owner = player; }

            /// <summary>
            /// Освобождает точку
            /// </summary>
            public void ReleasePoint()
            { owner = string.Empty; }

            /// <summary>
            /// Выводит информацию о точке
            /// </summary>
            /// <returns>информация о точке</returns>
            public string Show()
            {
                return string.Format("row: {0}, position: {1}, owner = {2}", row, position, owner);
            }

            /// <summary>
            /// Ряд
            /// </summary>
            public int Row
            { get { return row; } }
            /// <summary>
            /// Позиция
            /// </summary>
            public int Position
            { get { return position; } }
            /// <summary>
            /// Владелец
            /// </summary>
            public string Owner
            { get { return owner; } }

        }

        const int lineSize = 8;         //количество рядов точек
        const int circlesCount = 3;     //количество колец мельницы
        Spot[] spots;                   //массив точек игрового поля

        /// <summary>
        /// Конструктор
        /// </summary>
        public Field()
        {
            int centerLeftRow = lineSize / 2,
            centerRightRow = centerLeftRow + 1,
            centerUpPosition = lineSize / 2,
            centerDownPosition = centerUpPosition + 1;

            spots = new Spot[lineSize * circlesCount];
            int i = 0;
            int pos = 0;
            for (int row = 1; row < lineSize + 1; row++)
                for (int index = 1; index < 4; index++)
                {
                    if ((index == 2) && (row < centerLeftRow))
                        pos = centerUpPosition;
                    if ((index == 2) && (row > centerRightRow))
                        pos = centerDownPosition;
                    if ((index != 2) && (row < centerLeftRow))
                        pos = (index % 3) * row + (index / 3) * (lineSize + 1 - row);
                    if ((index != 2) && (row > centerRightRow))
                        pos = (index % 3) * (lineSize + 1 - row) + (index / 3) * row;
                    if ((row == centerLeftRow) || (row == centerRightRow))
                        pos = (row / centerRightRow) * centerRightRow + index;
                    spots[i] = new Spot(row, pos);
                    i++;
                }
        }

        /// <summary>
        /// Копирующий конструктор
        /// </summary>
        /// <param name="f"></param>
        public Field(Field f)
        {
            spots = new Spot[24];
            for (int i = 0; i < f.spots.Length; i++)
                spots[i] = new Spot(f.spots[i].Row, f.spots[i].Position, f.spots[i].Owner);
        }

        /// <summary>
        /// Пробует поместить фишку на заданную точку поля
        /// </summary>
        /// <param name="row">ряд</param>
        /// <param name="pos">столбец</param>
        /// <param name="player">кем выполняется попытка</param>
        /// <returns>успех/провал операции</returns>
        public bool TryPlace(int row, int pos, string player)
        {
            int index = IndexOf(row, pos);
            if (index == -1)
                return false;
            if (spots[index].Owner != string.Empty)
                return false;

            spots[index].TakePoint(player);
            return true;
        }

        /// <summary>
        /// Попытка взять фишку с заданной точки
        /// </summary>
        /// <param name="row">ряд</param>
        /// <param name="pos">столбец</param>
        /// <param name="player">кем выполняется попытка</param>
        /// <returns>успех/провал операции</returns>
        public bool TryTake(int row, int pos, string player)
        {
            int index = IndexOf(row, pos);
            if (index == -1)
                return false;
            if (spots[index].Owner == player)
                return true;
            return false;
        }

        /// <summary>
        /// Попытка перемещения фишки в заданную точку поля
        /// </summary>
        /// <param name="fromRow">ряд исходной точки</param>
        /// <param name="fromPos">столбец исходной точки</param>
        /// <param name="toRow">ряд точки назначения</param>
        /// <param name="toPos">столбец точки назначения</param>
        /// <param name="player">кем выполняется попытка</param>
        /// <returns>успех/провал операции</returns>
        public bool TryMove(int fromRow, int fromPos, int toRow, int toPos, string player)
        {
            int fromIndex = IndexOf(fromRow, fromPos);
            int toIndex = IndexOf(toRow, toPos);

            //нельзя двигать чужие фишки
            if (spots[fromIndex].Owner != player)
                return false;

            //нельзя перемещаться в занятую точку
            if (spots[toIndex].Owner != string.Empty)
                return false;

            //если у игрока на поле осталось 3 фишки, он имеет приоритет
            bool priority = false;
            if (GetCount(player) == 3)
                priority = true;

            //при наличии приоритета и пустой точки (проверка выполнена выше) - занять точку
            if (priority)
            {
                spots[fromIndex].ReleasePoint();
                spots[toIndex].TakePoint(player);
                return true;
            }

            //перемещение возможно только в точку с такой же позицией или таким же рядом
            if ((fromRow != toRow) && (fromPos != toPos))
                return false;

            //перемещение в одном ряду возможно только на единичное расстояние
            if (fromRow == toRow)
                if (Math.Abs(GetPositionInRow(fromRow, fromPos) - GetPositionInRow(toRow, toPos)) != 1)
                    return false;
            //перемещение в одном столбце возможно только на единичное расстояние
            if (fromPos == toPos)
                if (Math.Abs(GetPositionInColumn(fromRow, fromPos) - GetPositionInColumn(toRow, toPos)) != 1)
                    return false;

            //перемещаем фишку
            spots[fromIndex].ReleasePoint();
            spots[toIndex].TakePoint(player);
            return true;

        }

        /// <summary>
        /// Попытка захватить (убрать с поля) заданную фишку соперника
        /// </summary>
        /// <param name="row">ряд</param>
        /// <param name="pos">столбец</param>
        /// <param name="player">кем выполняется попытка</param>
        /// <returns>успех/провал операции</returns>
        public bool TryRemove(int row, int pos, string player)
        {
            //убирать можно только фишку соперника
            if ((spots[IndexOf(row, pos)].Owner != string.Empty) && (spots[IndexOf(row, pos)].Owner != player))
            {
                spots[IndexOf(row, pos)].ReleasePoint();
                return true;
            }
            return false;
        }

        /// <summary>
        /// Проверка конца игры
        /// </summary>
        /// <param name="p1">Игрок1</param>
        /// <param name="p2">Игрок2</param>
        /// <returns>конец/нет</returns>
        public bool isEnded(string p1, string p2)
        {
            //если у одного из игроков осталось меньше 3 фишек
            if ((GetCount(p1) < 3) || (GetCount(p2) < 3))
                return true;

            bool p1Stuck = true;
            bool p2Stuck = true;
            //если какому-либо игроку некуда походить
            foreach (Spot spot in spots)
            {
                if (isAnyEmptyNear(spot.Row, spot.Position))
                {
                    if (spot.Owner == p1)
                        p1Stuck = false;
                    if (spot.Owner == p2)
                        p2Stuck = false;
                }
                if (!p1Stuck && !p2Stuck)
                    break;
            }
            if (p1Stuck || p2Stuck)
                return true;
            return false;
        }

        /// <summary>
        /// Сравнение собранных мельниц в текущем и предыдущем ходах
        /// </summary>
        /// <param name="prevField">поле в состоянии до хода</param>
        /// <param name="player">игрок, относительно которого выполняется проверка</param>
        /// <returns>количество собранных мельниц относительно предыдущего хода</returns>
        public int Compare(Field prevField, string player)
        {
            int wins = 0;
            List<int> current = this.Check(player);
            List<int> previous = prevField.Check(player);
            wins = current.Count;
            for (int i = 0; i < current.Count; i++)
                if (previous.IndexOf(current[i]) != -1)
                    wins--;
            return wins;
        }

        /// <summary>
        /// Получает коллекцию (список) собранных рядов
        /// </summary>
        /// <param name="player">игрок, относительно которого выполняется проверка</param>
        /// <returns>коллекция собранных мельниц. Ряды со знаком +, столбцы со знаком -.</returns>
        private List<int> Check(string player)
        {
            List<int> lines = new List<int>();
            for (int row = 1; row < 9; row++)
            {
                int counter = 0;
                for (int index = 1; index < 4; index++)
                    if (spots[(row - 1) * 3 + (index - 1)].Owner != player)
                        break;
                    else
                        counter++;
                if (counter == 3)
                    lines.Add(row);
            }
            for (int pos = 1; pos < 9; pos++)
            {
                int counter = 0;
                int index = 0;
                for (int i = 0; i < spots.Length; i++)
                {
                    if ((spots[i].Position == pos) && (spots[i].Owner != player))
                        break;
                    if ((spots[i].Position == pos) && (spots[i].Owner == player))
                    {
                        counter++;
                        index++;
                        if (index == 3)
                            break;
                    }
                }
                if (counter == 3)
                    lines.Add(-pos);
            }
            return lines;
        }

        /// <summary>
        /// Проверяет наличие хотя бы одной свободной точки по соседству с данной
        /// </summary>
        /// <param name="row">ряд</param>
        /// <param name="pos">столбец</param>
        /// <returns>есть свободная точка рядом или нет</returns>
        private bool isAnyEmptyNear(int row, int pos)
        {
            int index = IndexOf(row, pos);
            int indexInRow = GetPositionInRow(row, pos);
            int indexInColumb = GetPositionInColumn(row, pos);

            //просматриваем соседние точки в этом же ряду и в этом же столбце
            foreach (Spot spot in spots)
                if (((Math.Abs(GetPositionInRow(spot.Row, spot.Position) - GetPositionInRow(spots[index].Row, spots[index].Position)) == 1) && (spot.Row == row))
                    || ((Math.Abs(GetPositionInColumn(spot.Row, spot.Position) - GetPositionInColumn(spots[index].Row, spots[index].Position)) == 1) && (spot.Position == pos)))
                    if (spot.Owner == string.Empty)
                        return true;

            return false;
        }

        /// <summary>
        /// Вычисляет количество фишек игрока на поле
        /// </summary>
        /// <param name="owner">игрок</param>
        /// <returns>количество фишек</returns>
        private int GetCount(string owner)
        {
            int counter = 0;
            for (int i = 0; i < spots.Length; i++)
                if (spots[i].Owner == owner)
                    counter++;
            return counter;
        }

        /// <summary>
        /// Вычисляет индекс точки в ряду
        /// </summary>
        /// <param name="row">ряд</param>
        /// <param name="pos">столбец</param>
        /// <returns>индекс в данном ряду (от 1 до 3)</returns>
        private int GetPositionInRow(int row, int pos)
        {
            int index = IndexOf(row, pos);
            if (index == -1)
                return -1;
            if (index == 0)
                return 1;
            if (index == 23)
                return 3;

            if (spots[index - 1].Row != row)
                return 1;
            if (spots[index + 1].Row != row)
                return 3;
            return 2;
        }

        /// <summary>
        /// Вычисляет индекс точки в столбце
        /// </summary>
        /// <param name="row">ряд</param>
        /// <param name="pos">столбец</param>
        /// <returns></returns>
        private int GetPositionInColumn(int row, int pos)
        {
            int index = IndexOf(row, pos);
            if (index == -1)
                return -1;
            if (index == 0)
                return 1;
            if (index == 23)
                return 3;

            int counter = 0;
            for (int i = index + 1; i < spots.Length; i++)
                if (spots[i].Position == pos)
                    counter++;
            if (counter == 0)
                return 3;
            if (counter == 1)
                return 2;
            return 1;
        }

        /// <summary>
        /// Вычисляет индекс точки в массиве точек поля
        /// </summary>
        /// <param name="r">ряд</param>
        /// <param name="p">столбец</param>
        /// <returns>индекс в массиве точек поля</returns>
        public int IndexOf(int r, int p)
        {
            for (int i = 0; i < spots.Length; i++)
                if ((spots[i].Row == r) & (spots[i].Position == p))
                    return i;
            return -1;
        }

        /// <summary>
        /// Вывод состояния всех точек поля
        /// </summary>
        /// <returns>Массив строк-информации по каждой точке</returns>
        public string[] Show()
        {
            string[] s = new string[spots.Length];
            for (int i = 0; i < spots.Length; i++)
                s[i] = spots[i].Show();
            return s;
        }

        /// <summary>
        /// Вывод информации о точке
        /// </summary>
        /// <param name="row">ряд</param>
        /// <param name="pos">столбец</param>
        /// <returns>Строка с информацией о точке</returns>
        public string ShowSpot(int row, int pos)
        {
            int i = IndexOf(row, pos);
            return spots[i].Show();
        }

    }
}
